package com.yunmai.web.common.advice;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.yunmai.account.center.sdk.vo.AccountGuestSession;
import com.yunmai.account.center.sdk.vo.AccountSession;
import com.yunmai.web.common.context.ContextHolder;
import com.yunmai.web.services.IRequestMonitorService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.util.Enumeration;
import java.util.Optional;

/**
 * @author WangChen
 * @since 2021-02-21 16:13
 **/
@Component
@Aspect
public class LogAspect {

    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    /** 换行符 */
    private static final String LINE_SEPARATOR = System.lineSeparator();
    private static final Duration MAX_REQUEST_TIME = Duration.ofSeconds(1);

    @Autowired
    private IRequestMonitorService requestMonitorService;

    @Pointcut("execution(* com.yunmai.web.controller.*Controller.*(..))")
    public void printLog() {
    }

    @Around("printLog()")
    public Object handleProtocol(ProceedingJoinPoint joinPoint) throws Throwable {

        long protocolStart = System.currentTimeMillis();

        try{
            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)
                    RequestContextHolder.getRequestAttributes();

            if (ObjectUtils.isEmpty(servletRequestAttributes)){
                return joinPoint.proceed();
            }
            HttpServletRequest request = servletRequestAttributes.getRequest();
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();

            String requestBody = resolveRequestBody(signature, joinPoint);

            resolveRequestLog(request, requestBody);

            Object proceed = joinPoint.proceed();

            resolveResponseLog(request, proceed, protocolStart);

            return proceed;

        } finally {
            ContextHolder.remove();
        }

    }


    /**
     * 处理请求log
     * @param request
     * @param requestBody
     * @return
     */
    private void resolveRequestLog(HttpServletRequest request, String requestBody){

        StringBuilder stringBuilder = new StringBuilder();

        stringBuilder.append(handleRequestHeader(request));

        stringBuilder
                .append(LINE_SEPARATOR)
                .append("body:{").append(LINE_SEPARATOR)
                .append("accountId:").append(Optional.ofNullable(ContextHolder.getSession()).orElseGet(AccountGuestSession::new).getAccountId()).append(LINE_SEPARATOR)
                .append("orgId:").append(Optional.ofNullable(ContextHolder.getSession()).orElseGet(AccountGuestSession::new).getOrgId()).append(LINE_SEPARATOR)
                .append("request method:").append(request.getMethod()).append(LINE_SEPARATOR)
                .append("request protocol:").append(request.getRequestURI()).append(LINE_SEPARATOR)
                .append("request body:").append(requestBody).append(LINE_SEPARATOR)
                .append("}");

        logger.info(stringBuilder.toString());

    }

    /**
     * 处理请求头
     * @param request
     * @return
     */
    private String handleRequestHeader(HttpServletRequest request){

        Enumeration<String> headerNames = request.getHeaderNames();

        StringBuilder stringBuilderReq = new StringBuilder();
        stringBuilderReq
                .append(LINE_SEPARATOR)
                .append("header:{")
                .append(LINE_SEPARATOR);
        while (headerNames.hasMoreElements()) {
            String headName = headerNames.nextElement();
            String header = request.getHeader(headName);
            stringBuilderReq.append(headName).append(":").append(header).append(LINE_SEPARATOR);
        }
            stringBuilderReq.append("}");
        return stringBuilderReq.toString();
    }

    /**
     * 处理请求体
     * @param signature
     * @param joinPoint
     * @return String
     */
    private String resolveRequestBody(MethodSignature signature, ProceedingJoinPoint joinPoint){

        String[] parameterNames = signature.getParameterNames();

        Object[] args = joinPoint.getArgs();

        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("[");
        for (int i = 0; i < args.length; i++) {
            if (i == args.length - 1){
                stringBuilder.append(parameterNames[i]).append(":").append(args[i]);
            } else {
                stringBuilder.append(parameterNames[i]).append(":").append(args[i]).append(" ,");
            }
        }
        stringBuilder.append("]");
        return stringBuilder.toString();
    }


    /**
     * 处理响应体
     * @param request
     * @param proceed
     * @param protocolStart
     */
    private void resolveResponseLog(HttpServletRequest request, Object proceed, long protocolStart){

        StringBuilder stringBuilderRes = new StringBuilder();

        stringBuilderRes
                .append(LINE_SEPARATOR)
                .append("response:{").append(LINE_SEPARATOR)
                .append("accountId:").append(Optional.ofNullable(ContextHolder.getSession()).orElseGet(AccountGuestSession::new).getAccountId()).append(LINE_SEPARATOR)
                .append("orgId:").append(Optional.ofNullable(ContextHolder.getSession()).orElseGet(AccountGuestSession::new).getOrgId()).append(LINE_SEPARATOR)
                .append("request method:").append(request.getMethod()).append(LINE_SEPARATOR)
                .append("request protocol:").append(request.getRequestURI()).append(LINE_SEPARATOR)
                .append("response body:").append(JSON.toJSONString(proceed)).append(LINE_SEPARATOR)
                .append("protocol time:").append(handleProtocolTime(protocolStart, request.getRequestURI())).append(LINE_SEPARATOR)
                .append("}");

        logger.info(stringBuilderRes.toString());

    }


    /**
     * 处理协议时间
     * @param protocolStart
     * @param protocolName
     * @return
     */
    private long handleProtocolTime(long protocolStart, String protocolName){

        long protocolTime = System.currentTimeMillis() - protocolStart;

        if (protocolTime > MAX_REQUEST_TIME.toMillis()){
            logger.info("[ProtocolTimeout][protocol = {}][time = {}]", protocolName, protocolTime);
            requestMonitorService.saveTimeOutRequest(protocolName, protocolTime);
        }

        return protocolTime;
    }

}
